require 'fileutils'
require 'base64'
require 'time'
require 'logger'
require 'rexml/document'

require 'converter_ruleset'

include REXML
include ConverterRuleset

class Z2TConverter
  attr_accessor :output, :ttxml_type
  attr_accessor :migrational
  attr_accessor :blog_name, :blog_title, :blog_description
  attr_accessor :ttxml_filename

  def initialize(filename)
    @logger = Logger.new($stdout)

    @migrational = 'false'
    @ttxml_type = 'tattertools/1.1'
    @filename = filename
    @comment_convert_rule = {
      "is_secret" => Rename.new("secret") {|v| v =~ /Y/ ? "1" : "0"},
      "content" => Ignore.new,
      "regdate" => Rename.new("written") {|v| Time.parse(v).to_i},
      "nick_name" => NewElement.new("commenter", "name"),
      "ipaddress" => NewElement.new("commenter", "ip")
    }
    @post_convert_rule = {
      "allow_comment" => Rename.new("acceptComment") {|v| v =~ /Y/ ? "1" : "0"},
      "allow_trackback" => Rename.new("acceptTrackback") {|v| v =~ /Y/ ? "1" : "0"},
      "is_secret" => Rename.new("visibility") {|v| v =~ /Y/ ? "private" : "public"},
      "content" => Ignore.new,
      "title" => Ignore.new,
      "regdate" => Multify.new(["created", "modified", "published"]) {|v| Time.parse(v).to_i},
    }
    @attach_convert_rule = {
      "filename" => Multify.new(["label", "name"]) {|v| v},
      "download_count" => Rename.new("downloads"),
      "file" => Strip.new("content")
    }
  end

  def convert
    # TTXML document
    ttroot = ttxml_document

    @filename.each do |filename|
      @logger.info("Converting #{filename}...")

      zbdoc = Document.new(File.open(filename))

      # Add category
      category = Element.new("category", ttroot)
      category_name = Element.new("name", category)
      category_name.text = File.basename(filename, ".xml")

      # post element에 대한 변환 과정
      convert_post_element(zbdoc.root, ttroot, category_name.text)

      yield(filename) if block_given?
    end

    @output = $stdout if @output.nil?
    formatter = Formatters::Default.new
    formatter.write(ttroot, @output)
  end

  def ttxml_document
    ttdoc = Document.new
    ttdoc << XMLDecl.new("1.0", "UTF-8")
    ttroot = Element.new("blog", ttdoc)
    ttroot.add_attribute("type", @ttxml_type)
    ttroot.add_attribute("migrational", @migrational)

    ttroot
  end

  def convert_post_element(zbroot, ttroot, category_name)
	posts = zbroot.get_elements("post")

	i = 0
	post_count = posts.size

    posts.each do |post|
      # 제로보드에 있는 XML element 처리
      post.each_element do |e|
        e.text = get_text(e)
        if "comments".eql?(e.name)
		  @logger.info("Comment count: #{e.to_a.size}")
          convert_element(post, "comments", @comment_convert_rule)
        elsif "attaches".eql?(e.name)
		  @logger.info("Attachement count: #{e.to_a.size}")
          convert_element(post, "attaches", @attach_convert_rule) do |attach|
            Element.new("enclosure", attach).text = '0'
            Element.new("attached", attach).text = Time.now.to_i

            attach.name = "attachment"
            content = attach.get_elements("content")[0]
            size = Base64.decode64(content.text).length

            attach.add_attribute("size", size.to_s)
          end
        else
		  if "title".eql?(e.name)
			@logger.info(e.text)
		  end
          rule = @post_convert_rule[e.name]
          if rule.nil?
            post.delete_element(e)
          else
            rule.execute(post, e)
          end
        end
      end

      # 제로보드에는 없는 rule 적용
      Sequence.new("id").execute(post, nil)
      Element.new("category", post).text = category_name

      ttroot << post
	  i += 1
	  @logger.info("#{i}/#{post_count}")
    end
  end

  def convert_element(parent, element_name, rule_set)
    children = parent.get_elements(element_name)
    parent.delete_element(children[0])

    children[0].each_element do |child|
      child.each_element do |item|
        item.text = get_text item

        rule = rule_set[item.name]
        if rule.nil?
          child.delete_element(item)
        else
          rule.execute(child, item)
        end
      end

      yield(child) if block_given?


      parent << child
    end
  end

  # 1. Zeroboard XML에서는 모든 데이터를 base64로 인코딩하기 때문에
  #   ttxml로 값을 전달하기 위해서는 base64 디코딩을 해야한다.
  #
  # 2. TTXML import할 때 PHP의 XML 파서가 일부 HTML entity만 유효하다고
  #   판단하기 때문에 유효한 HTML entity만 통과하고 나머지는 공백문자로
  #   바꿔준다.
  def get_text(item)
    decoded = Base64.decode64(item.text)
    decoded.gsub(/\&(\w+);/) do |v|
      (v =~ /amp|lt|gt|quot|apos/) ? v : ""
    end
  end
  private :get_text

end
